/*--------------------------------------------------------------------------------
* Copyright (c) 2019,西北农林科技大学信息学院计算机科学系
* All rights reserved.
*
* 文件名称：main.c
* 文件标识：见配置管理计划书
* 摘要：读取汉字点阵字库的演示代码。
*
* 当前版本：1.0
* 作者：耿楠
* 完成日期：2022年12月18日
*
* 取代版本：无
* 原作者：
* 完成日期：
--------------------------------------------------------------------------------*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include <iconv.h>

/* 宏定义 */
#define MSG_LEN 80
#define HZROW 16
#define HZCOL 16

/* 16位位段读取两字节数据，并得到每一个位的信息 */
/* 注意使用unsigned char，以确保只占2个字节(确保读入的字节数正确) */
typedef struct
{
    unsigned char a0: 1;
    unsigned char a1: 1;
    unsigned char a2: 1;
    unsigned char a3: 1;
    unsigned char a4: 1;
    unsigned char a5: 1;
    unsigned char a6: 1;
    unsigned char a7: 1;
    unsigned char a8: 1;
    unsigned char a9: 1;
    unsigned char aa: 1;
    unsigned char ab: 1;
    unsigned char ac: 1;
    unsigned char ad: 1;
    unsigned char ae: 1;
    unsigned char af: 1;
} BYTETYPE;

/* 中文字符数据结构 */
typedef struct
{
    unsigned char ch[3];             /* 中文字符(两字节) */
    BYTETYPE bitdata[HZROW];         /* 字模数据 */
    char dispchar[HZROW][HZCOL + 1]; /* 显示字符字符串数组 */
} HZCHAR;

/* 函数原型 */
int GetHZKCode(HZCHAR *, const char *, const char *); /* 从HZK16汉字字模库中读取一个汉字字符的数据 */

/* 字符串处理 */
int HZProc(HZCHAR *, const char *, const char *, const char *);

/* 结果输出 */
void PrintHZK(const HZCHAR *, int);

/* 测试 */
int main()
{
    char zh[MSG_LEN] = "信工学子";

    /* 由于UTF-8是用三个字节来表示一个汉字的，需要转换成2个字节表示 */
    /*====UTF-8编译转换为GB码，若源代码文件为GB编译则不需要这些代码====*/
    char zh_gb[MSG_LEN] = {0};
    char *pin = zh;
    char *pout = zh_gb;
    size_t inlen = strlen(zh) + 1;
    size_t outlen = MSG_LEN;
    iconv_t cd = iconv_open("GB2312", "UTF-8");
    if(cd == (iconv_t)(-1))
    {
        iconv_close(cd);
        perror("iconv_open");
    }
    if ((int) iconv(cd, &pin, &inlen, &pout, &outlen) == -1)
    {
        iconv_close(cd);
        perror("iconv()");
    }
    iconv_close(cd);

    size_t len = strlen(zh_gb);
    /*====================================================*/

    HZCHAR *pch = malloc(len * sizeof(HZCHAR)); /* 申请空间 */

    HZProc(pch, zh_gb, "HZK16", " *"); /* 读取并处理字模数据 */

    PrintHZK(pch, len / 2); /* 输出，注意汉字长度要除2 */

    free(pch); /* 释放内存 */

    return 0;
}

/* 函数定义 */
/*-----------------------------------------------------------------------------------------------
// 名称: int GetHZCode(HZCHAR *pch, const char *filename, const char *otab)
// 功能: 从字模文件中读入1个中文字符的字模数据
// 算法: 从指定文件中读取字模结构体字符成员的字模数据，并对读入的数据分析后，
//       按字模显示字符查找表填充字模结构体中的可显示字符二维数组。
//       该算法采用"位与"和"移位"操作实现。
// 参数:
//       [HZCHAR *pch] ------------ 字符数据结构体指针
//       [const char *filename] --- 字模文件名称字符串指针
//       [const char *otab] ------- 字模显示字符查找表指针(字符串)
// 返回: [int]  --- 成功返回1，否则返回0
// 作者: 耿楠
// 日期: 2022年12月18日
//---------------------------------------------------------------------------------------------*/
int GetHZCode(HZCHAR *pch, const char *filename, const char *otab)
{
    /* 参数有效性检验 */
    if(pch == NULL || filename == NULL || otab == NULL)
    {
        return 0;
    }

    FILE *fp;

    /* 打开字模文件 */
    fp = fopen(filename, "rb");
    if(fp == NULL)
    {
        printf("Can't open data file!");
        return 0;
    }

    /* 计算需要读取字符的字模数据偏移地址 */
    unsigned long pos = 0L;

    /* 计算汉字字模数据偏移地址 */
    /*区码=内码(高字节)-0xA0  位码=内码(低字节)-0xA0*/
    unsigned int rownum = pch->ch[0] - 0xA0;
    unsigned int colnum = pch->ch[1] - 0xA0;
    pos = (94 * (rownum - 1) + (colnum - 1)) * 32L; /*计算该汉字在字库中偏移量*/
    /* 文件定位 */
    fseek(fp, pos, SEEK_SET);
    /* 读入数据(HZROW * HZCOL / 8 个字节) */
    fread(pch->bitdata, HZROW * HZCOL / 8, 1, fp);

    /* 将字模数据转换为显示字符 */
    int i;
    for(i = 0; i < HZROW; i++)
    {
        /* 逐位赋值 */
        pch->dispchar[i][0] = otab[pch->bitdata[i].a7];
        pch->dispchar[i][1] = otab[pch->bitdata[i].a6];
        pch->dispchar[i][2] = otab[pch->bitdata[i].a5];
        pch->dispchar[i][3] = otab[pch->bitdata[i].a4];
        pch->dispchar[i][4] = otab[pch->bitdata[i].a3];
        pch->dispchar[i][5] = otab[pch->bitdata[i].a2];
        pch->dispchar[i][6] = otab[pch->bitdata[i].a1];
        pch->dispchar[i][7] = otab[pch->bitdata[i].a0];
        pch->dispchar[i][8] = otab[pch->bitdata[i].af];
        pch->dispchar[i][9] = otab[pch->bitdata[i].ae];
        pch->dispchar[i][10] = otab[pch->bitdata[i].ad];
        pch->dispchar[i][11] = otab[pch->bitdata[i].ac];
        pch->dispchar[i][12] = otab[pch->bitdata[i].ab];
        pch->dispchar[i][13] = otab[pch->bitdata[i].aa];
        pch->dispchar[i][14] = otab[pch->bitdata[i].a9];
        pch->dispchar[i][15] = otab[pch->bitdata[i].a8];
        pch->dispchar[i][16] = '\0';
    }

    /* 关闭字模文件 */
    fclose(fp);

    return 1;
}

/*-----------------------------------------------------------------------------------------------
// 名称: int HZProc(HZCHAR *pch, const char *str, const char *filename, const char *otab)
// 功能: 处理录入的字字符串
// 参数:
//       [HZCHAR *pch] --- 字符数据结构体指针
//       [char *str] --- 要处理的字符串
//       [const char *filename] --- 字模文件文件名称
//       [const char *otab] --- 字模显示查找表
// 返回: [int]  --- 成功返回1，否则返回0
// 作者: 耿楠
// 日期: 2022年12月18日
//---------------------------------------------------------------------------------------------*/
int HZProc(HZCHAR *pch, const char *str,  const char *filename, const char *otab)
{
    /* 参数有效性检验 */
    if(str == NULL || pch == NULL || filename == NULL)
    {
        return 0;
    }

    /* 处理每一个字符 */
    while(*str != '\0')
    {
        pch->ch[0] = str[0];
        pch->ch[1] = str[1];
        pch->ch[2] = '\0';
        GetHZCode(pch, filename, otab);
        pch++;
        str += 2;
    }

    return 1;
}

/*-----------------------------------------------------------------------------------------------
// 名称: void PrintHZK(const HZCHAR *pch, int n)
// 功能: 用可显示字符显示字模结构体数组中的字符
// 算法: 将字模结构体数组中的所有字符的每行可显示字符串拼装成一个完整字符串后输出。
// 参数:
//       [const HZCHAR *pch] --- 字符数据结构体数组指针
//       [int n] --- 字符个数
// 返回: [void]  --- 无
// 作者: 耿楠
// 日期: 2022年12月18日
//---------------------------------------------------------------------------------------------*/
void PrintHZK(const HZCHAR *pch, int n)
{
    /* 参数有效性检验 */
    if(pch == NULL)
    {
        return;
    }

    char **pstr = NULL;

    /* 分析字符串指针数组 */
    pstr = malloc(HZROW * sizeof(char *));
    if(pstr == NULL)
    {
        printf("Not enough memory!");
        return;
    }
    /* 分配每行字条串数组 */
    int i, j;
    for(i = 0; i < HZROW; i++)
    {
        pstr[i] = malloc((n * HZCOL + 1) * sizeof(char));
        if(pstr[i] == NULL)
        {
            printf("Not enough memory!");
            return;
        }
        memset(pstr[i], 0, (n * HZCOL + 1) * sizeof(char));
    }

    /* 将每个字符的显示字符串拼成一个字符串 */
    for(i = 0; i < HZROW; i++)
    {
        for(j = 0; j < n; j++)
        {
            strcat(pstr[i], pch[j].dispchar[i]); /* 拼装 */
        }
    }

    /* 输出拼成的字符串 */
    for(i = 0; i < HZROW; i++)
    {
        puts(pstr[i]);
    }

    /* 释放内存 */
    for(i = 0; i < HZROW; i++)
    {
        free(pstr[i]);
    }

    free(pstr);
}
